// Version: 0.1
// ANTLR Version: 3.1.1
// Date: 2008.12.15
//
// Description: Super calculator, written in Java.
// 
// java  -cp ..\lib\antlr-3.1.1.jar; org.antlr.Tool Calculator.g
// javac -cp ..\lib\antlr-3.1.1.jar; *.java
// java  -cp ..\lib\antlr-3.1.1.jar; CalculatorParser
// =======================================================================================
// Author: wong.tong@gmail.com


grammar Calculator;

options {
	language='Java';
    k = 1;
    output = AST;
}

tokens {
	PLUS 	= '+';
	MINUS	= '-';
	MULT	= '*';
	DIV		= '/';
	MOD		= '%';
}

@header {
package astudy.parser.calc;
}
@lexer::header {
package astudy.parser.calc;
}
@members {
	private enum Type {
		FLOAT("float"),
		INTEGER("integer"),
		String("string");
		private String type;
		Type(String type) {
			this.type = type;
		}
		@Override
		public String toString() { return this.type; }
	}
}
/*------------------------------------------------------------------
 * LEXER RULES
 *------------------------------------------------------------------*/
COMMA			: ',';
PLUS			: '+';
MINUS			: '-';
LPAREN			: '(' ;
RPAREN			: ')' ;
DOT				: '.';
NUMBER			: INTEGER|FLOAT|FLOAT('e'|'E')(PLUS|MINUS)*INTEGER;
WHITESPACE 		: ('\t'|' '|'\r'|'\n'|'\u000C')+ 	{ $channel = HIDDEN; } ;
EOL				: '=';
fragment DIGIT
	:	 '0'..'9';
fragment INTEGER
	:	 (DIGIT)+;
fragment FLOAT	
	:	 (DIGIT)*DOT(DIGIT)*;
fragment VARIABLE
	:	('a'..'z'|'A'..-'Z')+('a'..'z'|'A'..-'Z'|'0'-'9')*;
//functions
ABS			: 'abs';
SIN			: 'sin';
COS			: 'cos';
SQRT		: 'sqrt';
POW			: 'pow';
LN			: 'ln';
LOG			: 'log';

/*------------------------------------------------------------------
 * PARSER RULES
 *------------------------------------------------------------------*/

//call -> expression EOL
call returns [Number value, Type type] 
	: expression EOL {
		$type = $expression.type;
		$value = $expression.value;
	}
	;

//expression -> term ((PLUS|MINUS) term)*
expression returns [Number value, Type type]
	: t=term {
		$type = $t.type;
		$value = $t.value;
	} (
		PLUS t=term {
			if ($type == Type.INTEGER && $t.type == Type.INTEGER) {
				$type = Type.INTEGER;
				$value = $value.intValue() + $t.value.intValue();
			} else {
				$type = Type.FLOAT;
				$value = $value.doubleValue() + $t.value.doubleValue();
			}
		 } | MINUS t=term {
			if ($type == Type.INTEGER && $t.type == Type.INTEGER) {
				$type = Type.INTEGER;
				$value = $value.intValue() - $t.value.intValue();
			} else {
				$type = Type.FLOAT;
				$value = $value.doubleValue() - $t.value.doubleValue();
			}
		 } 
	)*
	;

//term -> factor ((MULT|DIV) factor)*
term returns [Number value, Type type]
	: f=factor {
		$type = $f.type;
		$value = $f.value;
	} (
		MULT f=factor {
			if ($type == Type.INTEGER && $f.type == Type.INTEGER) {
				$type = Type.INTEGER;
				$value = $value.intValue() * $f.value.intValue();
			} else {
				$type = Type.FLOAT;
				$value = $value.doubleValue() * $f.value.doubleValue();
			}
		} | DIV f=factor {
			if ($type == Type.INTEGER && $f.type == Type.INTEGER) {
				if ($f.value.intValue() == 0) {
					throw new ArithmeticException("line " + $DIV.line + ":" + $DIV.pos + " Divide by Zero!");
				} else {
					$type = Type.INTEGER;
					$value = $value.intValue() / $f.value.intValue();
				}
			} else {
				if ($f.value.doubleValue() == 0.0f) {
					throw new ArithmeticException("line " + $DIV.line + ":" + $DIV.pos + " Divide by Zero!");
				} else {
					$type = Type.FLOAT;
					$value = $value.doubleValue() / $f.value.doubleValue();
				}
			}
		} | MOD f=factor {
			if ($type == Type.INTEGER && $f.type == Type.INTEGER) {
				$type = Type.INTEGER;
				int a = $value.intValue();
				int b = $f.value.intValue();
				$value = a-a/b*b ;
			} else {
				throw new ArithmeticException("line " + $MOD.line + ":" + $MOD.pos + " MOD operation is valid for Integers only!");
			}
		}
	)* 
	;

atom returns [Number value, Type type]
	: NUMBER {
		String str = $NUMBER.text.trim();
		int index = $NUMBER.text.indexOf(".");
		if (index == str.length()-1) {
			str = str.substring(0, index);
		}
		if (str.indexOf(".") > -1) {
			$type = Type.FLOAT;
			$value = Double.valueOf(str);
		} else {
			$type = Type.INTEGER;
			$value = Integer.valueOf(str);
		}
	} | LPAREN expression RPAREN {
		$type = $expression.type;
		$value = $expression.value;
	} | function {
		$type = $function.type;
		$value = $function.value;
	}
	;

//factor -> NUMBER
factor returns [Number value, Type type]
	: PLUS? atom {
		$type = $atom.type;
		$value = $atom.value;
	} | MINUS atom {
		$type = $atom.type;
		if ($type  == Type.FLOAT) {
			$value = -$atom.value.doubleValue();
		} else {	//integer
			$value = -$atom.value.intValue();
		}
	}
	;
	
function returns [Number value, Type type]
	: 
	ABS LPAREN expression RPAREN {
		$type = $expression.type;
		if ($expression.type == Type.INTEGER) {
			$value = Math.abs($expression.value.intValue());
		} else {
			$value = Math.abs($expression.value.doubleValue());
		}
	} | SIN LPAREN expression RPAREN {
		$type = Type.FLOAT;
		$value = Math.sin($expression.value.doubleValue());
	} | COS LPAREN expression RPAREN {
		$type = Type.FLOAT;
		$value = Math.cos($expression.value.doubleValue());
	} | SQRT LPAREN expression RPAREN {
		double v = $expression.value.doubleValue();
		if (v < 0.0f) {
			throw new ArithmeticException("line " + $SQRT.line + ":" + $SQRT.pos + " Square Root of negative number [" + v + "]!");
		} else {
			$type = Type.FLOAT;
			$value = Math.sqrt(v);
		}
	} | LN LPAREN expression RPAREN {
		double v = $expression.value.doubleValue();
		if (v <= 0.0f) {
			throw new ArithmeticException("line " + $LN.line + ":" + $LN.pos + " Ln of negative number or Zero [" + v + "]!");
		} else {
			$type = Type.FLOAT;
			$value = Math.log(v);
		}
	} | LOG LPAREN expression RPAREN {
		double v = $expression.value.doubleValue();
		if (v <= 0.0f) {
			throw new ArithmeticException("line " + $LOG.line + ":" + $LOG.pos + " Log of negative number or Zero [" + v + "]!");
		} else {
			$type = Type.FLOAT;
			$value = Math.log10(v);
		}
	} | POW  LPAREN a=expression COMMA b=expression RPAREN {
		$type = Type.FLOAT;
		$value = Math.pow($a.value.doubleValue(), $b.value.doubleValue());
	}
	;
